﻿using Arch.Core;
using Meow.Core.AssetManage;
using Meow.Core.Simulation;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System;
using System.Collections.Generic;
using System.Reflection.Metadata;

namespace Meow.Core;

internal interface IScene
{
    /// <summary>
    /// Runs independently on a background thread
    /// </summary>
    /// <param name="tick">total ticks from game start</param>
    public void Tick(int tick);

    /// <summary>
    /// Update as fast as possible
    /// </summary>
    public void Update(GameTime gameTime);

    public void Render(in GraphicsDeviceManager gdm, GameTime gameTime, int tick);
}

public class Scene : IScene, IDisposable
{
    public readonly World World;

    /// <summary>
    /// Scene's own content should be loaded by itself
    /// Use this own ContentManager
    /// </summary>
    protected ContentManager Content;
    public readonly MeshCache MeshCache;
    public readonly MaterialCache MaterialCache;
    public readonly TextureCache TextureCache;
    public readonly List<IAssetCache> AssetCaches = new List<IAssetCache>();

    public float TotalGameTime { get; protected set; }
    public float UpdateScaleFactor { get; protected set; }

    public bool TickPause { get; protected set; }
    public bool UpdatePause { get; protected set; }
    public bool RenderPause { get; protected set; }

    public bool ToUnload { get; protected set; }


    public readonly List<IRenderSystem> RenderSystems = new List<IRenderSystem>();
    public readonly List<IUpdateSystem> UpdateSystems = new List<IUpdateSystem>();
    public readonly List<ITickSystem> TickSystems = new List<ITickSystem>();

    public Scene(Engine engine)
    {
        World = World.Create();

        Content = new ContentManager(engine.Services);
        Content.RootDirectory = "";

        TotalGameTime = 0f;
        UpdateScaleFactor = 1f;

        MeshCache = new MeshCache();
        MaterialCache = new MaterialCache();
        TextureCache = new TextureCache();

        AssetCaches.Add(MeshCache);
        AssetCaches.Add(MaterialCache);
        AssetCaches.Add(TextureCache);
    }

    bool inited = false;

    /// <summary>
    /// If the scene has been added to the engine before engine.Run().
    /// Engine will handle this.
    /// Otherwise, we should call it by ourself.
    /// </summary>
    public virtual void LoadContents()
    {
        inited = true;

        foreach (var cache in AssetCaches)
            cache.Init();
    }

    /// <summary>
    /// Call before first Update and after LoadContents.
    /// 
    /// If the scene has been added to the engine before engine.Run().
    /// Engine will handle this.
    /// Otherwise, we should call it by ourself.
    /// </summary>
    public virtual void BeginRun()
    {
    }

    /// <summary>
    /// should handle by engine
    /// </summary>
    public virtual void Render(in GraphicsDeviceManager gdm, GameTime gameTime, int tick)
    {
        if (!inited)
            throw new Exception("Scene shoud complete init before update");

        if (RenderPause)
            return;

        for (var i = 0; i < RenderSystems.Count; i++)
        {
            RenderSystems[i].Render(gdm, gameTime, TotalGameTime, tick);
        }
    }

    /// <summary>
    /// should handle by engine
    /// </summary>
    public virtual void Tick(int tick)
    {
        if (!inited)
            throw new Exception("Scene shoud complete init before update");

        if (TickPause)
            return;

        for (var i = 0; i < TickSystems.Count; i++)
        {
            TickSystems[i].Tick(tick);
        }
    }

    /// <summary>
    /// should handle by engine
    /// </summary>
    public virtual void Update(GameTime gameTime)
    {
        if (!inited)
            throw new Exception("Scene shoud complete init before update");

        TotalGameTime += (float)gameTime.ElapsedGameTime.TotalSeconds;

        if (UpdatePause)
            return;

        for (var i = 0; i < UpdateSystems.Count; i++)
        {
            UpdateSystems[i].Update(gameTime, TotalGameTime, UpdateScaleFactor);
        }
    }

    public virtual void Dispose()
    {
        World.Destroy(World);

        foreach (var cache in AssetCaches)
            cache.Dispose();

        Content.Dispose();
    }

    public virtual void Initialize()
    {
        LoadContents();
        BeginRun();
    }
}
